﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;

namespace MongoCamp
{
    /// <summary>
    /// MongoDB generic access and operate class
    /// </summary>
    public static class MongoHelper
    {
        /// <summary>
        /// Get single record By primary key or IMongoQuery
        /// </summary>
        /// <typeparam name="TEntityClass">entity class or BsonDocument</typeparam>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="primaryKeyOrIMongoQuery"></param>
        /// <returns></returns>
        public static TEntityClass FindOne<TEntityClass>(MongoServerPack mongoServer, string collectionName, object primaryKeyOrIMongoQuery)
        {
            TEntityClass entity;
            var db = mongoServer.GetDefaultDB();

            using (db.RequestStart())
            {
                var collection = db.GetCollection<TEntityClass>(collectionName);

                if (primaryKeyOrIMongoQuery is IMongoQuery)
                    entity = collection.FindOneAs<TEntityClass>(primaryKeyOrIMongoQuery as IMongoQuery);
                else
                    entity = collection.FindOneByIdAs<TEntityClass>(primaryKeyOrIMongoQuery.ToBsonValue());
            }

            return entity;
        }

        /// <summary>
        /// Get single record
        /// </summary>
        /// <typeparam name="TEntityClass">entity class or BsonDocument</typeparam>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="query"></param>
        /// <param name="fields"></param>
        /// <param name="sortBy"></param>
        /// <returns></returns>
        public static TEntityClass FindOne<TEntityClass>(MongoServerPack mongoServer, string collectionName, IMongoQuery query, IMongoFields fields = null, IMongoSortBy sortBy = null)
        {
            if (fields == null && sortBy == null)
                return FindOne<TEntityClass>(mongoServer, collectionName, (object)query);

            return FindList<TEntityClass>(mongoServer, collectionName, query, fields, sortBy, 1, 0)
                .FirstOrDefault();
        }

        /// <summary>
        /// Get all record
        /// </summary>
        /// <typeparam name="TEntityClass">entity class or BsonDocument</typeparam>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <returns></returns>
        public static IList<TEntityClass> FindAll<TEntityClass>(MongoServerPack mongoServer, string collectionName)
        {
            var list = new List<TEntityClass>();
            var db = mongoServer.GetDefaultDB();

            using (db.RequestStart())
            {
                var collection = db.GetCollection<TEntityClass>(collectionName);
                list.AddRange(collection.FindAllAs<TEntityClass>());
            }

            return list;
        }

        /// <summary>
        /// get record list
        /// </summary>
        /// <typeparam name="TEntityClass"></typeparam>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="query"></param>
        /// <param name="fields"></param>
        /// <param name="sortBy"></param>
        /// <param name="pageSize"></param>
        /// <param name="pageIndex"></param>
        /// <returns></returns>
        public static IList<TEntityClass> FindList<TEntityClass>(MongoServerPack mongoServer, string collectionName, IMongoQuery query, IMongoFields fields = null, IMongoSortBy sortBy = null, int? pageSize = null, int? pageIndex = null)
        {
            var recordCount = -1;
            return FindList<TEntityClass>(mongoServer, collectionName, query, fields, sortBy, pageSize, pageIndex, ref recordCount);
        }

        /// <summary>
        /// get record list
        /// </summary>
        /// <typeparam name="TEntityClass"></typeparam>
        /// <param name="recordCount"></param>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="query"></param>
        /// <param name="fields"></param>
        /// <param name="sortBy"></param>
        /// <param name="pageSize"></param>
        /// <param name="pageIndex"></param>
        /// <returns></returns>
        public static IList<TEntityClass> FindList<TEntityClass>(out int recordCount, MongoServerPack mongoServer, string collectionName, IMongoQuery query, IMongoFields fields = null, IMongoSortBy sortBy = null, int? pageSize = null, int? pageIndex = null)
        {
            recordCount = 0;
            return FindList<TEntityClass>(mongoServer, collectionName, query, fields, sortBy, pageSize, pageIndex, ref recordCount);
        }

        private static IList<TEntityClass> FindList<TEntityClass>(MongoServerPack mongoServer, string collectionName, IMongoQuery query, IMongoFields fields, IMongoSortBy sortBy, int? pageSize, int? pageIndex, ref int recordCount)
        {
            var list = new List<TEntityClass>();
            var db = mongoServer.GetDefaultDB();

            using (db.RequestStart())
            {
                var collection = db.GetCollection<TEntityClass>(collectionName);

                if (recordCount == 0)
                    recordCount = (int)collection.Count(query);

                list.AddRange(collection.FindAs<TEntityClass>(query)
                    .SetPageList(fields, sortBy, pageSize, pageIndex));
            }

            return list;
        }

        /// <summary>
        /// get collection count
        /// </summary>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="query"></param>
        /// <returns>long</returns>
        public static long GetCount(MongoServerPack mongoServer, string collectionName, IMongoQuery query)
        {
            var count = 0L;
            var db = mongoServer.GetDefaultDB();

            using (db.RequestStart())
            {
                var collection = db.GetCollection(collectionName);
                count = (query == null ? collection.Count() : collection.Count(query));
            }

            return count;
        }

        /// <summary>
        /// update collection
        /// </summary>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="query"></param>
        /// <param name="update"></param>
        /// <param name="flags"></param>
        /// <param name="writeConcern"></param>
        /// <returns></returns>
        public static WriteConcernResult Update(MongoServerPack mongoServer, string collectionName, IMongoQuery query, IMongoUpdate update, UpdateFlags flags = UpdateFlags.Multi, WriteConcern writeConcern = null)
        {
            WriteConcernResult result;
            var db = mongoServer.GetDefaultDB();
            
            using (db.RequestStart())
            {
                var collection = db.GetCollection(collectionName);
                
                var options = new MongoUpdateOptions() { Flags = flags };
                if (writeConcern != null)
                    options.WriteConcern = writeConcern;
                
                result = collection.Update(query, update, options);
            }

            return result;
        }

        /// <summary>
        /// update document
        /// </summary>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="query"></param>
        /// <param name="updateDocument">document</param>
        /// <param name="flags"></param>
        /// <param name="writeConcern"></param>
        /// <returns></returns>
        public static WriteConcernResult Update(MongoServerPack mongoServer, string collectionName, IMongoQuery query, BsonDocument updateDocument, UpdateFlags flags = UpdateFlags.Upsert, WriteConcern writeConcern = null)
        {
            if (updateDocument.IsNullOrEmpty()) return null;

            var update = new UpdateBuilder();

            foreach (var item in updateDocument)
                update.Set(item.Name, item.Value);

            return Update(mongoServer, collectionName, query, update, flags, writeConcern);
        }

        /// <summary>
        /// remove collection by query
        /// </summary>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="query"></param>
        /// <param name="flags"></param>
        /// <param name="writeConcern"></param>
        /// <returns></returns>
        public static WriteConcernResult Remove(MongoServerPack mongoServer, string collectionName, IMongoQuery query,
                                                RemoveFlags flags = RemoveFlags.None, WriteConcern writeConcern = null)
        {
            return Remove(false, mongoServer, collectionName, query, flags, writeConcern);
        }

        /// <summary>
        /// remove collection all record
        /// </summary>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="flags"></param>
        /// <param name="writeConcern"></param>
        /// <returns></returns>
        public static WriteConcernResult RemoveAll(MongoServerPack mongoServer, string collectionName, RemoveFlags flags = RemoveFlags.None, WriteConcern writeConcern = null)
        {
            return Remove(true, mongoServer, collectionName, Query.Null, flags, writeConcern);
        }

        private static WriteConcernResult Remove(bool allowRemoveAll, MongoServerPack mongoServer, string collectionName, IMongoQuery query, RemoveFlags flags = RemoveFlags.None, WriteConcern writeConcern = null)
        {
            if (!allowRemoveAll && query == null) return null;

            WriteConcernResult result;
            var db = mongoServer.GetDefaultDB();

            using (db.RequestStart())
            {
                var collection = db.GetCollection(collectionName);
                result = allowRemoveAll ? collection.RemoveAll(writeConcern) : collection.Remove(query, flags, writeConcern);
            }

            return result;
        }


        /// <summary>
        /// save record to collection
        /// </summary>
        /// <typeparam name="TEntityClass">entity class or BsonDocument</typeparam>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <param name="entity"></param>
        /// <param name="flags"></param>
        /// <param name="writeConcern"></param>
        /// <returns></returns>
        public static WriteConcernResult Save<TEntityClass>(MongoServerPack mongoServer, string collectionName, TEntityClass entity, InsertFlags flags = InsertFlags.None, WriteConcern writeConcern = null)
        {
            WriteConcernResult result;
            var db = mongoServer.GetDefaultDB();

            using (db.RequestStart())
            {
                var collection = db.GetCollection<TEntityClass>(collectionName);

                var options = new MongoInsertOptions() { Flags = flags };
                if (writeConcern != null)
                    options.WriteConcern = writeConcern;

                result = collection.Save(entity);
            }

            return result;
        }

        /// <summary>
        /// insert single record to collection
        /// </summary>
        /// <typeparam name="TEntityClass">entity class or BsonDocument</typeparam>
        /// <param name="mongoServer">MongoDB server config</param>
        /// <param name="collectionName">the collection name</param>
        /// <param name="entity">object</param>
        /// <param name="flags">InsertFlags</param>
        /// <param name="writeConcern"></param>
        /// <returns>WriteConcernResult</returns>
        public static WriteConcernResult Insert<TEntityClass>(MongoServerPack mongoServer, string collectionName, TEntityClass entity, InsertFlags flags = InsertFlags.None, WriteConcern writeConcern = null)
        {
            var list = InsertBatch(mongoServer, collectionName, new List<TEntityClass>() {entity}, flags, writeConcern);
            return list.FirstOrDefault();
        }

        /// <summary>
        /// insert record list to collection
        /// </summary>
        /// <typeparam name="TEntityClass">entity class or BsonDocument</typeparam>
        /// <param name="mongoServer">MongoDB server config</param>
        /// <param name="collectionName">the collection name</param>
        /// <param name="entityList">record list</param>
        /// <param name="flags">InsertFlags</param>
        /// <param name="writeConcern"></param>
        /// <returns>WriteConcernResult Array</returns>
        public static IEnumerable<WriteConcernResult> InsertBatch<TEntityClass>(MongoServerPack mongoServer, string collectionName, IList<TEntityClass> entityList, InsertFlags flags = InsertFlags.None, WriteConcern writeConcern = null)
        {
            IEnumerable<WriteConcernResult> result;
            var db = mongoServer.GetDefaultDB();

            using (db.RequestStart())
            {
                var collection = db.GetCollection<TEntityClass>(collectionName);

                var options = new MongoInsertOptions() {Flags = flags};
                if (writeConcern != null)
                    options.WriteConcern = writeConcern;

                result = collection.InsertBatch(entityList, options);
            }

            return result;
        }

        /// <summary>
        /// drop collection
        /// </summary>
        /// <param name="mongoServer"></param>
        /// <param name="collectionName"></param>
        /// <returns></returns>
        public static CommandResult DorpCollection(MongoServerPack mongoServer, string collectionName)
        {
            CommandResult result;
            var db = mongoServer.GetDefaultDB();

            using (db.RequestStart())
                result = db.DropCollection(collectionName);

            return result;
        }

        /// <summary>
        /// rename collection
        /// </summary>
        /// <param name="mongoServer"></param>
        /// <param name="oldCollectionName"></param>
        /// <param name="newCollectionName"></param>
        /// <param name="dropTarget"></param>
        /// <returns></returns>
        public static CommandResult RenameCollection(MongoServerPack mongoServer, string oldCollectionName, string newCollectionName, bool dropTarget = false)
        {
            CommandResult result;
            var db = mongoServer.GetDefaultDB();

            using (db.RequestStart())
                result = db.RenameCollection(oldCollectionName, newCollectionName, dropTarget);

            return result;
        }
    }
}
